#include "../include/keyboard.h"
#include "../include/terminal.h"
#include "../include/kc.h"
#include "../include/kasm.h"

//Definicion de makes y breaks no imprimibles
#define MAKE_SHIFTL 0x2A
#define MAKE_SHIFTR 0x36
#define BREAK_SHIFTL 0xAA
#define BREAK_SHIFTR 0xB6
#define MAKE_ALTL 0x38
#define BREAK_ALTL 0xB8
#define MAKE_CTRLL 0x1D
#define BREAK_CTRLL 0x9D
#define MAKE_ENTER 0x1C
#define BREAK_ENTER 0x9C
#define MAKE_CAPSLOCK 0x3A
#define MAKE_NUMLOCK 0X45
#define MAKE_UP	0x48
#define MAKE_DOWN 0x50
#define MAKE_PG_UP	0x49
#define MAKE_PG_DOWN 0x51
#define MAKE_DIV 0x35
#define MAKE_DEL 0x53
#define MAKE_F1 0x3B
#define MAKE_F8 0x42

#define LED_CAPS_ON 0x04
#define LED_CAPS_OFF (~LED_CAPS_ON)
#define LED_NUM_ON 0x02
#define LED_NUM_OFF (~LED_NUM_ON)
#define LEDS_OFF 0x00

//El code de enhanced se usa para algunas teclas
// (como el delete, el enter del numpad, por ej)
#define ENHANCED_CODE 0xE0
static char enhancedPressed = 0;

//variables que guardan el estado de varias teclas, y que afectaran
// el resultado final de lo que se vera en pantalla
static char shiftLeftPressed = 0;
static char shiftRightPressed = 0;
static char ctrlLeftPressed = 0;
static char altLeftPressed = 0;
static char capslockPressed = 0;
static char numlockPressed = 0;
static char keyboardLeds = LEDS_OFF;

//vector que mapea los scancodes con sus correspondientes ascii
//la primer fila son los valores comunes, la segunda con shift apretado
static unsigned char scanCodes[2][96]= {
	{0, 0, '1', '2', '3', '4', '5', '6',
	'7', '8', '9', '0', '-', '=', '\r', 0,
	'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
	'o', 'p', '[', ']', '\n', 0, 'a', 's',
	'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
	'\'', '`', 0, '#', 'z', 'x', 'c', 'v',
	'b', 'n', 'm', ',', '.', '/', 0, '*',
	0, ' ', 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, '7',
	'8', '9', '-', '4', '5', '6', '+', '1',
	'2', '3', '0', '.', 0, 0, '\\', 0,
	0, 0, 0, 0, 0, 0, 0, 0}
	,
	{0, 0, '!', '\"', '#', '$', '%', '^',
	'&', '*', '(', ')', '_', '+', '\r', 0,
	'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I',
	'O', 'P', '{', '}', '\n', 0, 'A', 'S',
	'D', 'F', 'G', 'H', 'J', 'K', 'L', ':',
	'@', 0, 0, '~', 'Z', 'X', 'C', 'V',
	'B', 'N', 'M', '<', '>', '/', 0, '*',
	0, ' ', 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, '7',
	'8', '9', '-', '4', '5', '6', '+', '1',
	'2', '3', '0', '.', 0, 0, '|', 0,
	0, 0, 0, 0, 0, 0, 0, 0}
};

void pressCapsLock();
void pressNumLock();

int
isPrintable(unsigned char c) {
	return c != 0;
}

//funcion que devuelve el ascii correspondiente al scancode
char
getScanCode(unsigned char code) {
	//primero chequeo que el code este entre los que tengo mapeados
	if (code >= 1 && code <= 96)
	{
		if(!(!numlockPressed && code>=70 && code<=96 && code!=78 && code!=74))
			return scanCodes[shiftLeftPressed | shiftRightPressed][code];
	}
	// si no esta, devuelvo 0
	return 0;
}

//funcion para reiniciar la cpu
void
reboot() {
	{
		writeWait();

		//primero tengo que enviarle el comando 0xD1 al teclado
		// para escribir en el output port
		_outport(0x64, 0xD1);

		writeWait();

		//y despues le envio el comando para reiniciar, que tiene
		// su primer bit en 0 para realizar el reset
		_outport(0x60, 0xFE);
	}
}

//traduce el scancode que recibe del teclado y realiza la accion
//correspondiente. Devuelve 1 si escribio al stdin
int
keyTranslator(buffer_t * stdin, unsigned char code) {

	//veo si me pasaron una tecla enhanced
	if (enhancedPressed == 1) {
		enhancedPressed = 0;
		//si es la flecha para arriba o para abajo, mando el ascii y
		//salgo de la funcion
		if (code == MAKE_UP || code == MAKE_DOWN) {
			putcharbuff(stdin, (code == MAKE_UP) ? ASCII_KEY_UP : ASCII_KEY_DOWN);
			return 1;
		}

		//si es el page up o page down, mando el ascii y
		//salgo de la funcion
		if (code == MAKE_PG_DOWN || code == MAKE_PG_UP) {
			putcharbuff(stdin, (code == MAKE_PG_DOWN) ? ASCII_PG_DOWN : ASCII_PG_UP);
			return 1;
		}

		//si tocaron CRTL + ALT + DELETE, hago un reboot
		if (code == MAKE_DEL && ctrlLeftPressed && altLeftPressed) {
			reboot();
			return 0; //vuelvo por si llegara a fallar algo y no reiniciara
		}

		//si es enhanced, voy a ignorar las teclas que no sean las siguientes
		if (code != MAKE_ENTER && code != MAKE_DIV)
			return 0;
	}

	//si tocan de F1 a F8 + alt, cambio la terminal
	if (code >= MAKE_F1 && code <= MAKE_F8 && altLeftPressed) {
		switchTTY(code - MAKE_F1);
		return 0;
	}

	//en el caso particular de enhanced, marco el flag, para el proximo scancode
	switch (code) {
		case ENHANCED_CODE: enhancedPressed = 1; break;
		case MAKE_SHIFTL: shiftLeftPressed = 1; break;
		case MAKE_SHIFTR: shiftRightPressed = 1; break;
		case BREAK_SHIFTL: shiftLeftPressed = 0; break;
		case BREAK_SHIFTR: shiftRightPressed = 0; break;
		case MAKE_ALTL: altLeftPressed = 1; break;
		case BREAK_ALTL: altLeftPressed = 0; break;
		case MAKE_CTRLL: ctrlLeftPressed = 1; break;
		case BREAK_CTRLL: ctrlLeftPressed = 0; break;
		case MAKE_CAPSLOCK: pressCapsLock(); break;
		case MAKE_NUMLOCK: pressNumLock(); break;
	}

	char c = getScanCode(code);
	if (isPrintable(c)) {
		if(capslockPressed) {
			if (shiftLeftPressed || shiftRightPressed) {
				c = tolower(c);
			}
			else {
				c = toupper(c);
			}
		}

		putcharbuff(stdin, c);
		return 1;
	}

	return 0;
}

/*----------------------------------------------------------------------
 Funciones de espera de respuesta del teclado. Obtenidas de la pagina
 http://houbysoft.com/download/ps2mouse.html. Se cambiaron nombres de
 variables y funciones usadas por funciones propias.
*/
//espera a que el teclado responda que se le puede escribir
void writeWait() {
	unsigned int timeOut = 100000;
    while(timeOut--)
    {
      if((_inport(0x64) & 2)==0)
      {
        return;
      }
    }
    return;
}
//espera a que el teclado responda que se puede leer de su buffer
void readWait() {
	unsigned int timeOut = 100000;
	while(timeOut--)
	{
	  if((_inport(0x64) & 1)==1)
	  {
		return;
	  }
	}
	return;
}
//----------------------------------------------------------------------


//Funcion de cambio de estado de los leds
void
changeLed(){
	writeWait();

	//le aviso al teclado que voy a cambiar un led
	_outport(0x60, 0xED);
	readWait();
	_inport(0x60);	//leo el ack del teclado
	writeWait();

	//le paso al teclado el estado actual de los leds
	_outport(0x60, keyboardLeds);
	readWait();
	_inport(0x60); //leo el ack del teclado
}

void
pressCapsLock() {
	capslockPressed = !capslockPressed;

	//actualizo el led de capslock
	if (capslockPressed) {
		keyboardLeds = keyboardLeds | LED_CAPS_ON;//prendo el led de capslock
		changeLed();
	}
	else {
		keyboardLeds = keyboardLeds & LED_CAPS_OFF;//apago el led de capslock
		changeLed();
	}
}

void
pressNumLock() {
	numlockPressed = !numlockPressed;

	//actualizo el led de numlock
	if (numlockPressed) {
		keyboardLeds = keyboardLeds | LED_NUM_ON;//prendo el led de numlock
		changeLed();
	}
	else {
		keyboardLeds = keyboardLeds & LED_NUM_OFF;//apago el led de numlock
		changeLed();
	}
}

//Funcion de inicializacion de teclado
void
initKeyboard() {
	//apago todos los leds de teclado
	keyboardLeds = LEDS_OFF;
	changeLed();
}
